<?php

	class DataCache {
		static private $instance;
		private $CacheDir = '';
		private $CachePathes = array();
		private $LifeTime = 3600;
		private static $Memcache;

		//=============================================================================

		function __construct() {
			global $G;

			if (empty(self::$Memcache))
			{
				MemcacheInit();
				self::$Memcache = $G['memcache'];
			}

			$this->CacheDir = TOP_DIR.'/tmp/data_cache';
		}

		//=============================================================================

		static public function getInstance() {
			if (!self::$instance)
				self::$instance = new self;

			return self::$instance;
		}

		//=============================================================================

		public function MemcacheLive()
		{
			if (!empty(self::$Memcache) && !empty(self::$Memcache->connection))
			{
				return true;
			}

			if (!file_exists($this->CacheDir))
			{
				mkdir($this->CacheDir, 0777, 1);
				chmod($this->CacheDir, 0777);
			}

			return false;
		}

		//=============================================================================

		public function Increment($key, $inc_by = 1, $create_lifetime = 0)
		{
			if ($this->MemcacheLive())
			{
				if ( ($value = self::$Memcache->get($key)) !== false)
				{
					//got the value, increment only new format!
					if ($this->IsNewFormat($value))
					{
						$value['_dc_cache']+= $inc_by;
						self::$Memcache->set($key, $value, false, $value['_dc_cache_time']);
						$ret = $value['_dc_cache'];

						return $ret;
					}
				}
				elseif ($create_lifetime)
				{
					//no value, but we should create it
					$this->Set($key, $inc_by, $create_lifetime);
				}
			}

			return false; //means something bad happened
		}

		//=============================================================================

		public function IsNewFormat($ret)
		{
			if (is_array($ret) && !empty($ret['_dc_life_end']) && !empty($ret['_dc_cache_time']))
			{
				return true;
			}

			return false;
		}

		//=============================================================================

		public function Set($key, $data, $lifetime = 0, $use_files = false) {

			global $G;
			if ($G["inst_def"]->do_caching === "false") {
				return true;
			}

			if ($lifetime <= 0)
			{
				$lifetime = $this->LifeTime;
			}

			if (!$use_files && $this->MemcacheLive())
			{
				//prepare data
				$cache = array('_dc_cache' => $data, '_dc_life_end' => time() + $lifetime, '_dc_cache_time' => $lifetime);
				return self::$Memcache->set($key, $cache, false, $lifetime*2);
			}
			else
			{
				if (!($path = $this->GetPathToCache($key)))
				{
					return false;
				}

				$time = time();
				$options = array('end' => $time + $lifetime);

				$path_tmp = $path.'_tmp.tmp';
				$f = fopen($path_tmp, 'wb');
				if (!$f) return false;

				flock($f, LOCK_EX);
				fwrite($f, json_encode($options)."\n");
				fwrite($f, json_encode($data));
				flock($f, LOCK_UN);
				fclose($f);

				return rename($path_tmp, $path);
			}
		}

		//=============================================================================

		public function Get($key, $use_files = false) {

			global $G;
			if ($G["inst_def"]->do_caching === "false") {
				return false;
			}

			if (!$use_files && $this->MemcacheLive())
			{
				$ret = self::$Memcache->get($key);
				if (!empty($ret))
				{
					//new format?
					if ($this->IsNewFormat($ret))
					{
						//check lifetime
						if (time() > $ret['_dc_life_end'])
						{
							//expired, save the same for a longer time for other connections
							$this->Set($key, $ret['_dc_cache'], $ret['_dc_cache_time']);

							return false;
						}
						else
						{
							//still alive
							return $ret['_dc_cache'];
						}
					}
				}

				return $ret;
			}
			else
			{
				if (!($path = $this->GetPathToCache($key)))
				{
					return false;
				}
				if (!file_exists($path))
				{
					return false;
				}

				//get first line
				$f = fopen($path, 'rb');
				if (!$f) return false;

				$frow = fgets($f, 4096);
				$options = json_decode($frow, 1);
				if (empty($options['end']) || $options['end'] <= time()) return false;

				$cache = '';
				while($row = fgets($f))
				{
					$cache .= $row;
				}

				return json_decode($cache, 1);
			}
		}

		//=============================================================================

		private function GetPathToCache($key)
		{
			if (empty($key)) return false;
			if (!isset($this->CachePathes[$key]))
			{
				$mkey = basename($key).'.cache';
				$mkey = str_replace(array('_', '::'), array('/', '/'), $mkey);
				$mpath = $this->CacheDir.'/'.$mkey;
				$mdir = dirname($mpath);
				if (!is_dir($mdir))
				{
					mkdir($mdir, 0777, 1);
					chmod($mdir, 0777);
				}
				$this->CachePathes[$key] = $mpath;
			}

			return $this->CachePathes[$key];
		}

		//=============================================================================

		public function Delete($key)
		{
			if ($this->MemcacheLive())
			{
				return self::$Memcache->delete($key);
			}
			else
			{
				if (!($path = $this->GetPathToCache($key)))
				{
					return false;
				}

				return unlink($path);
			}
		}

		//=============================================================================

		public function WaitingForCache($key) {
			$c = 0;
			while(($res = $this->Get($key)) == -1) {
				$c++;
				//sleep for 0.5 sec
				usleep(500000);
				if ($c > 20) {
					$res = false;
					break;
				}
			}

			return $res;
		}

	}
?>
